EventHorizon = {}
EventHorizonDB = {
	point = {'CENTER', 'UIParent', 'CENTER'},
}
local EventHorizon = EventHorizon
EventHorizon.config = {}
EventHorizon.barconfigs = {}
EventHorizon.barframes = {}
EventHorizon.db = EventHorizonDB

local UnitDebuff = UnitDebuff

local isPTR = tonumber((select(2,GetBuildInfo()))) > 9551
local PLAYER_TALENT_UPDATE = isPTR and 'PLAYER_TALENT_UPDATE' or 'CHARACTER_POINTS_CHANGED'

local eventhandler
local spellbase = {}
local EventHorizonFrame = CreateFrame('Frame','EventHorizonFrame',UIParent)
local mainframe = CreateFrame('Frame',nil,EventHorizonFrame)
mainframe:SetAllPoints()

local activeFrames = {}

local playerguid
local onepixelwide = 1

local function printhelp(...) if select('#',...)>0 then return tostring((select(1,...))), printhelp(select(2,...)) end end
local function debug(...)
	print(...)
end
local function print(...)
	ChatFrame1:AddMessage('EventHorizon: '..strjoin(',',printhelp(...)))
end

-- Dispatch event to method of the event's name.
local function EventHandler(self, event, ...)
	local f = self[event]
	if f then 
		f(self,...) 
	end 
end

-- Helper function for unique debuffs that accepts a list of aura names.
-- Intended usage is for unique debuffs like Mangle - Cat/Mangle - Bear/Trauma, which overwrite each other.
local function UnitDebuffUnique(unitid, auras)
	for i,auraname in ipairs(auras) do
		local name, rank, icon, count, debuffType, duration, expirationTime, isMine, isStealable = UnitDebuff(unitid, auraname)
		if name then
			return name, rank, icon, count, debuffType, duration, expirationTime, isMine, isStealable
		end
	end
end

function spellbase:NotInteresting(unitid, spellname) 
	return unitid ~= 'player' or spellname ~= self.spellname
end

--[[
Indicators represent a point in time. There are different types. The type determines the color and position.
--]]
local colors = {
	sent = {1,1,1,1},
	tick = {1,1,1,1},
	casting = {0,1,0,0.3},
	cooldown = {1,1,1,0.3},
	debuffmine = {1,1,1,0.3},
	debuff = {0.6,0.6,0.6,0.3},
	playerbuff = {0,1,0,0.3},
	default = {1,1,1,1},
}
local layouts = {
	tick = {
		top = {'TOP',0,0},
		bottom = {'TOP', 0, -5},
	},
	smalldebuff = {
		top = {'TOP', 0, -3},
		bottom = {'TOP', 0, -6},
	},
	cantcast = {
		top = {'TOP', 0, -6},
	},
	default = {
		top = {'TOP', 0, -5},
		bottom = {'BOTTOM', 0, 0},
	},
}

local past, future
local barwidth, barheight, barspacing, scale
local bartexture, barbgcolor
local texturedbars

EventHorizon.colors = colors
EventHorizon.layouts = layouts
function spellbase:AddIndicator(typeid, layoutid, time, usetexture)
	local indicator
	if #self.unused>0 then
		indicator = tremove(self.unused)
		indicator:ClearAllPoints()
		indicator.time = nil
		indicator.start = nil
		indicator.stop = nil
		indicator.happened = nil
	else
		indicator = self:CreateTexture(nil, 'ARTWORK')
	end
	-- Layout
	local layout = layouts[layoutid] or layouts.default
	indicator:SetPoint('TOP',self,unpack(layout.top))
	indicator:SetPoint('BOTTOM',self,unpack(layout.bottom))

	local color = colors[typeid] or colors.default
	if usetexture then
		indicator:SetTexture(bartexture)
		indicator:SetTexCoord(unpack(layout.texcoords))
	else
		indicator:SetTexture(1,1,1,1)
	end
	indicator:SetVertexColor(unpack(color))

	indicator:Hide()
	indicator:SetWidth(onepixelwide)
	indicator.time = time
	indicator.typeid = typeid
	if indicator then
		tinsert(self.indicators, indicator)
	end
	return indicator
end

function spellbase:AddSegment(typeid, layoutid, start, stop)
	local indicator = self:AddIndicator(typeid, layoutid, start, texturedbars)
	indicator.time = nil
	indicator.start = start
	indicator.stop = stop
	--print(start,stop)
	return indicator
end

function spellbase:Remove(indicator)
	if type(indicator)=='number' then
		local ind = self.indicators[indicator]
		ind:Hide()
		tinsert(self.unused, tremove(self.indicators, indicator))
	else
		for k=1,#self.indicators do
			if self.indicators[k]==indicator then
				indicator:Hide()
				tinsert(self.unused, tremove(self.indicators,k))
				break
			end
		end
	end
end

function spellbase:OnUpdate(elapsed)
	local now = GetTime()
	local diff = now+past

	-- spellframe.nexttick is used to schedule the creation of predicted ticks as soon as they scroll past now+future.
	if self.nexttick and self.nexttick <= now+future then
		if self.nexttick<=self.lasttick then
			self:AddIndicator('tick', 'tick', self.nexttick)
			self.latesttick = self.nexttick
			self.nexttick = self.nexttick + self.dot
		else
			self.nexttick = nil
		end
	end
	for k=#self.indicators,1,-1 do
		local indicator = self.indicators[k]
		local time = indicator.time
		if time then
			-- Example: 
			-- [-------|------->--------]
			-- past    now     time     future
			-- now=795, time=800, past=-3, then time is time-now-past after past.
			local p = (time-diff)*scale
			local remove = p<0 or (time<=now and indicator.typeid=='tick' and not indicator.happened)
			if remove then
				indicator:Hide()
				tinsert(self.unused, tremove(self.indicators,k))
			elseif p<=1 then
				indicator:SetPoint('LEFT', self, 'LEFT', p*barwidth, 0)
				indicator:Show()
			end
		else
			local start, stop = indicator.start, indicator.stop
			local p1 = (start-diff)*scale
			local p2 = (stop-diff)*scale
			if p2<0 then
				indicator:Hide()
				tinsert(self.unused, tremove(self.indicators,k))
			elseif 1<p1 then
				indicator:Hide()
			else
				indicator:Show()
				indicator:SetPoint('LEFT', self, 'LEFT', 0<=p1 and p1*barwidth or 0, 0)
				indicator:SetPoint('RIGHT', self, 'LEFT', p2<=1 and p2*barwidth+1 or barwidth, 0)
			end
		end
	end
end
-- /run e.mf:AddIndicator(GetTime())

function spellbase:UNIT_SPELLCAST_SENT(unitid, spellname, spellrank, spelltarget)
	--debug('UNIT_SPELLCAST_SENT',unitid, spellname, spellrank, spelltarget)
	local now = GetTime()
	if self:NotInteresting(unitid, spellname) then return end
	self:AddIndicator('sent', 'default', now)
end

function spellbase:UNIT_SPELLCAST_CHANNEL_START(unitid, spellname, spellrank)
	if self:NotInteresting(unitid, spellname) then return end
	local name, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(unitid)
	if not startTime or not endTime then return end
	startTime, endTime = startTime/1000, endTime/1000
	self.casting = self:AddSegment('casting', 'default', startTime, endTime)
	if self.numhits then
		local casttime = endTime - startTime
		local tick = casttime/self.numhits
		self.ticks = {}
		for i=1,self.numhits do
			tinsert(self.ticks, self:AddIndicator('tick', 'tick', startTime + i*tick))
		end
	end
end

function spellbase:UNIT_SPELLCAST_CHANNEL_UPDATE(unitid, spellname, spellrank)
	--debug('UNIT_SPELLCAST_CHANNEL_UPDATE',unitid, spellname, spellrank)
	if self:NotInteresting(unitid, spellname) then return end
	local name, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(unitid)
	if not startTime or not endTime then return end
	startTime, endTime = startTime/1000, endTime/1000
	if self.casting then
		self.casting.stop = endTime
	end
	local ticks = self.ticks
	if ticks then
		for i = #ticks,1,-1 do
			local tick = ticks[i]
			if tick.time > endTime then
				tick.time = past-1 -- flag for removal
				self.ticks[i] = nil
			end
		end
	end
end

function spellbase:UNIT_SPELLCAST_CHANNEL_STOP(unitid, spellname, spellrank)
	local now = GetTime()
	if self:NotInteresting(unitid, spellname) then return end
	if self.casting then
		self.casting.stop = now
		self.casting = nil
	end
	local ticks = self.ticks
	if ticks then
		for i = #ticks,1,-1 do
			local tick = ticks[i]
			if tick.time > now then
				tick.time = past-1 -- flag for removal
				self.ticks[i] = nil
			end
		end
		self.ticks = nil
	end
end

function spellbase:UNIT_SPELLCAST_START(unitid, spellname, spellrank, target)
	if self:NotInteresting(unitid, spellname) then return end
	local name, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(unitid)
	if not startTime or not endTime then return end
	startTime, endTime = startTime/1000, endTime/1000
	self.casting = self:AddSegment('casting', 'default', startTime, endTime)
end

function spellbase:UNIT_SPELLCAST_STOP(unitid, spellname, spellrank)
	local now = GetTime()
	if self:NotInteresting(unitid, spellname) then return end
	if self.casting then
		self.casting.stop = now
		self.casting = nil
	end
end

function spellbase:UNIT_SPELLCAST_DELAYED(unitid, spellname, spellrank)
	--debug('UNIT_SPELLCAST_CHANNEL_UPDATE',unitid, spellname, spellrank)
	if self:NotInteresting(unitid, spellname) then return end
	local name, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(unitid)
	if not startTime or not endTime then return end
	startTime, endTime = startTime/1000, endTime/1000
	if self.casting and self.stop then
		self.stop.time = endTime
	end
end

function spellbase:UNIT_SPELLCAST_SUCCEEDED(unitid, spellname, spellrank)
	if self:NotInteresting(unitid, spellname) then return end
	self.succeeded = GetTime()
end


function spellbase:UNIT_AURA(unitid)
	if unitid~=self.auraunit then return end
	local name, rank, icon, count, debuffType, duration, expirationTime, isMine, isStealable = self.AuraFunction(self.auraunit, self.auraname)
	isMine = isMine==1 or isMine=='player'
	local afflicted = name and (self.unique or isMine) and (not self.minstacks or count>=self.minstacks)
	local addnew
	local now = GetTime()
	local start
	if afflicted then
		start = expirationTime-duration
		if self.aurasegment then
			if expirationTime~=self.aurasegment.stop then
				if self.alwaysrefresh then
					-- The aura was refreshed.
					self.aurasegment.stop = expirationTime
				else
					-- The aura was replaced.
					self.aurasegment.stop = start-0.2
					if self.cast then
						self.cantcast.stop = start-0.2
					end
					self:RemoveTicksAfter(start)
					self.nexttick = nil
					addnew = true
				end
			end
		else
			addnew = true
			if self.internalcooldown then		-- If we have an internal cooldown set for an aura or trinket proc, display it
				local ready = now + self.internalcooldown
				self:AddSegment('cooldown', 'default', expirationTime, ready)
			end
		end
		if count>0 then
			self.stacks:SetFormattedText('%d',count)
		end
	else
		if self.aurasegment then
			if math.abs(self.aurasegment.stop - now)>0.3 then
				self.aurasegment.stop = now
				if self.cast then
					self.cantcast.stop = now-0.2
				end
				self:RemoveTicksAfter(now)
			end
			self.aurasegment = nil
			self.nexttick = nil
			self.stacks:SetText()
		end
	end
	self:UpdateDoT(addnew, isMine, now, start, expirationTime)
end

function spellbase:PLAYER_TARGET_CHANGED()
	if self.aurasegment then
		for i = #self.indicators,1,-1 do
			local ind = self.indicators[i]
			if ind.typeid == 'tick' or ind.typeid == 'cantcast' or ind.typeid == 'debuff' or ind.typeid == 'debuffmine' then
				self:Remove(i)
			end
		end
		self.aurasegment = nil
		self.nexttick = nil
		self.stacks:SetText()
	end

	if UnitExists('target') then
		self:UNIT_AURA('target')
	end
end

--[[
Refreshable debuffs are really ugly, because UnitDebuff won't tell us when the debuff was applied.
It gets complicated because a debuff might be [re]applied/refreshed when we're not looking.
Here's what can happen, and which point of time we have to assume as the start.
success applied -> applied
success applied refresh -> applied
applied success -> success
applied success refresh -> success
applied -> applied
applied refresh -> applied

So we need to keep track of every debuff currently applied, and the success events. 
When the target or debuff changes, we need to look at the time of the last success to see if we can trust UnitDebuff.
--]]
function spellbase:COMBAT_LOG_EVENT_UNFILTERED(time, event, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname)
	local now = GetTime()
	if event == 'SPELL_CAST_SUCCESS' then
		--debug('SPELL_CAST_SUCCESS',destguid)
		self.castsuccess[destguid] = now
	elseif event == 'SPELL_PERIODIC_DAMAGE' or event == 'SPELL_DAMAGE' then
		if UnitGUID('target')==destguid then
			local tick = self:AddIndicator('tick', 'tick', now)
			tick.happened = true
		end
	end
end

function spellbase:RemoveTicksAfter(min)
	local indicators = self.indicators
	for i = #indicators,1,-1 do
		local ind = indicators[i]
		if ind.typeid == 'tick' and ind.time>min then
			self:Remove(i)
		end
	end
end

function spellbase:UNIT_AURA_refreshable(unitid)
	if unitid~=self.auraunit then return end
	local name, rank, icon, count, debuffType, duration, expirationTime, isMine, isStealable = self.AuraFunction(self.auraunit, self.auraname)
	isMine = isMine==1 or isMine=='player'
	local afflicted = name and (self.unique or isMine) and (not self.minstacks or count>=self.minstacks)
	local addnew, refresh
	local now = GetTime()
	local guid = UnitGUID('target')
	-- First find out if the debuff was refreshed.
	local start
	if afflicted then
		start = expirationTime-duration
		if self.targetdebuff then
			if self.targetdebuff.stop == expirationTime then
				start = self.targetdebuff.start
			else
				-- Check for refresh. 
				if start < self.targetdebuff.stop then
					local totalduration = self.targetdebuff.stop - self.targetdebuff.start
					local lasttick = self.targetdebuff.stop - math.fmod(totalduration, self.dot)
					local success = self.castsuccess[guid]
					local not_recast = true -- Poisons are never actually recast, so we default to true here.
					if success then
						not_recast = math.abs(success-start)>0.5
					end
					if not_recast and start < lasttick then
						-- The current debuff was refreshed.
						start = self.targetdebuff.start
						refresh = true
					end
				end
			end
		end
		if self.aurasegment then
			if expirationTime~=self.aurasegment.stop and not refresh then
				-- The current debuff was replaced.
				self.aurasegment.stop = start-0.2
				self:RemoveTicksAfter(start)
				self.nexttick = nil

				--debug('replaced')
				addnew = true
			end
		else
			addnew = true
		end
		if count>0 then
			self.stacks:SetFormattedText('%d',count)
		end
	else
		if self.aurasegment then
			if math.abs(self.aurasegment.stop - now)>0.3 then
				-- The current debuff ended.
				self.aurasegment.stop = now
			end
			self:RemoveTicksAfter(now)
			self.aurasegment = nil
			self.targetdebuff = nil
			self.nexttick = nil
			self.stacks:SetText()
		end
	end
	self:UpdateDoT(addnew, isMine, now, start, expirationTime, refresh, guid)
end

function spellbase:UpdateDoT(addnew, isMine, now, start, expirationTime, refresh, guid)
	local addticks
	if addnew then
		--debug('addnew', start, expirationTime)
		local typeid = isMine and 'debuffmine' or 'debuff'
		if self.cast then
			self.aurasegment = self:AddSegment(typeid, 'smalldebuff', start, expirationTime)
			local casttime = select(7, GetSpellInfo(self.spellname))/1000
			self.cantcast = self:AddSegment(typeid, 'cantcast', start, expirationTime-casttime)
		else
			self.aurasegment = self:AddSegment(typeid, 'default', start, expirationTime)
		end
		-- Add visible ticks.
		if self.dot then
			addticks = start
		end
		if self.debuffs then
			-- Refreshable only.
			self.targetdebuff = {start=start, stop=expirationTime}
			self.debuffs[guid] = self.targetdebuff
		end
	elseif refresh then
		--debug('refresh', start, expirationTime)
		-- Note: refresh requires afflicted and self.targetdebuff. Also, afflicted and not self.debuff implies addnew.
		-- So we can get here only if afflicted and self.debuff and self.targetdebuff.
		self.aurasegment.stop = expirationTime
		self.targetdebuff.stop = expirationTime
		if self.latesttick then
			addticks = self.latesttick
		end
	end
	if addticks then
		local nexttick = addticks+self.dot
		self.nexttick = nil
		while nexttick<=expirationTime do
			if now+future<nexttick then
				self.nexttick = nexttick
				self.lasttick = expirationTime
				break
			end
			if now+past<=nexttick then
				local tick = self:AddIndicator('tick', 'tick', nexttick)
				if nexttick<=now then
					tick.happened = true
				end
				self.latesttick = nexttick
			end
			nexttick=nexttick+self.dot
		end
	end
end

function spellbase:PLAYER_REGEN_ENABLED()
	local thresh = GetTime() - 10
	local remove = {}
	for guid,data in pairs(self.debuffs) do
		if data.stop < thresh then
			tinsert(remove, guid)
		end
	end
	for _,guid in ipairs(remove) do
		--debug('removing',guid,self.spellname)
		self.debuffs[guid]=nil
	end
end

function spellbase:PLAYER_TARGET_CHANGED_refreshable()
	--debug(self.spellname, 'PLAYER_TARGET_CHANGED_refreshable')
	if self.aurasegment then
		--debug(self.spellname, 'removing old')
		for i = #self.indicators,1,-1 do
			local ind = self.indicators[i]
			if ind.typeid == 'tick' or ind.typeid == 'debuff' or ind.typeid == 'debuffmine' then
				self:Remove(i)
			end
		end
		self.aurasegment = nil
		self.targetdebuff = nil
		self.nexttick = nil
		self.stacks:SetText()
	end

	if UnitExists('target') then
		if UnitIsDead('target') then
			self.debuffs[UnitGUID('target')] = nil
		else
			self.targetdebuff = self.debuffs[UnitGUID('target')]
			--if self.targetdebuff then debug(self.spellname, 'have old') end
			self:UNIT_AURA('target')
			--if self.aurasegment then debug(self.spellname, 'added new') end
		end
	end
end

function spellbase:SPELL_UPDATE_COOLDOWN()
	local start, duration, enabled = GetSpellCooldown(self.spellname)
	local ready = enabled==1 and start~=0 and duration and start+duration
	if ready and duration>1.5 then
		-- The spell is on cooldown, but not just because of the GCD.
		if self.cooldown ~= ready then
			--if self.coolingdown and (self.coolingdown.stop>start or self.coolingdown.start<ready) then debug('overlapping cooldown', self.spellname, start, ready) end
			self.coolingdown = self:AddSegment('cooldown', 'default', start, ready)
			self.cooldown = ready
		end
	else
		if self.coolingdown then
			-- The spell was on cooldown, but now it's not (or it's on cooldown, but just because of the GCD).
			local now = GetTime()
			-- See when the cooldown is ready. If the spell is currently on GCD, check the GCD end; otherwise check now.
			if self.cooldown > (ready or now) then
				-- The cooldown ended earlier.
				self.coolingdown.stop = now
			end
			self.coolingdown = nil
		end
		self.cooldown = nil
	end
end

mainframe[PLAYER_TALENT_UPDATE] = function(self)
	--debug(PLAYER_TALENT_UPDATE)
	EventHorizon:CheckTalents()
end

do
	local function HaveRequiredTalent(config)
		local result = true
		if config.requiredTalent then
			-- If requiredPoints is present, currentRank==requiredPoints is required.
			-- Otherwise, currentRank>0 is required.
			local tab,index,requiredPoints = unpack(config.requiredTalent)
			local currentRank = (select(5,GetTalentInfo(tab,index))) or 0
			--debug(tab,index,requiredPoints or 'nil',currentRank)
			--if (requiredPoints and currentRank~=requiredPoints) or (not requiredPoints and currentRank==0) then
				-- TODO Create the frame anyways and register for talent changes.
			--	return
			--end
			if requiredPoints then
				result = currentRank == requiredPoints
			else
				result = currentRank > 0
			end
		end
		return result
	end

	function EventHorizon:CheckTalents()
		table.wipe(activeFrames)
		for i,config in ipairs(self.barconfigs) do
			local frameNeeded = HaveRequiredTalent(config)
			-- Check if there already is a frame
			local spellframe = self.barframes[i]
			local frameExists = spellframe~=nil
			--debug('checking frame',i,': frameExists',frameExists,', frameNeeded ',frameNeeded)

			if frameNeeded then
				if frameExists then
					spellframe:Activate()
				else
					spellframe = self:CreateSpellBar(config)
					self.barframes[i] = spellframe
				end
				table.insert(activeFrames, spellframe)
			else
				if frameExists then
					spellframe:Deactivate()
				end
			end
		end

		local activate = #activeFrames > 0
		self:Activate(activate)
		if activate then
			mainframe:UPDATE_SHAPESHIFT_FORM()
		end

		--[[
		for i,f in ipairs(activeFrames)do
			debug('active frame',i,': ',f.spellname)
		end
		--]]
	end
end

function mainframe:UPDATE_SHAPESHIFT_FORM()
	--debug('UPDATE_SHAPESHIFT_FORM', 'GetShapeshiftForm()',GetShapeshiftForm(),'GetShapeshiftForm(true)',GetShapeshiftForm(true), 'GetNumShapeshiftForms()',GetNumShapeshiftForms())
	local stance = GetShapeshiftForm()
	-- On PLAYER_LOGIN, GetShapeshiftForm() sometimes returns a bogus value (2 on a priest with 1 form). No need to act upon it.
	if not stance or (GetNumShapeshiftForms() and stance>GetNumShapeshiftForms()) then return end
	mainframe:SetHeight(1)
	local numframes = 0
	for i,spellframe in ipairs(activeFrames) do
		local shown = spellframe:IsShown()
		if spellframe.stance == stance and not shown then
			-- Show
			spellframe:Show()
			shown = true
		elseif spellframe.stance and spellframe.stance ~= stance and shown then
			-- Hide
			spellframe:Hide()
			shown = false
		end

		if shown then
			numframes = numframes+1
			spellframe.icon:ClearAllPoints()
			spellframe.icon:SetPoint('TOPLEFT', mainframe, 'TOPLEFT', 0, -(numframes-1) * (barheight+barspacing))
		end
	end
	if numframes>0 then
		mainframe:SetHeight(numframes * (barheight+barspacing) - barspacing)
		mainframe:Show()
	else
		mainframe:Hide()
	end
	return true
end
mainframe.UPDATE_SHAPESHIFT_FORMS = mainframe.UPDATE_SHAPESHIFT_FORM

-- GCD indicator
local gcdIndicator, gcdSpellName
do
	local gcdend

	local function OnUpdate(frame, elapsed)
		if gcdend then
			local now = GetTime()
			if gcdend<=now then
				gcdend = nil
				gcdIndicator:Hide()
			else
				local diff = now+past
				local p = (gcdend-diff)*scale
				if p<=1 then
					gcdIndicator:SetPoint('RIGHT', frame, 'RIGHT', (p-1)*barwidth+onepixelwide, 0)
					gcdIndicator:Show()
				end
			end
		end
	end
	function mainframe:SPELL_UPDATE_COOLDOWN()
		local start, duration = GetSpellCooldown(gcdSpellName)
		if start and duration and duration>0 then
			gcdend = start+duration
			mainframe:SetScript('OnUpdate', OnUpdate)
		else
			gcdend = nil
			gcdIndicator:Hide()
			mainframe:SetScript('OnUpdate', nil)
		end
	end
end


-- Dispatch the CLEU.
function mainframe:COMBAT_LOG_EVENT_UNFILTERED(time, event, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname)
	if srcguid~=playerguid or event:sub(1,5)~='SPELL' then return end
	local spellframe = self.framebyspell[spellname]
	if spellframe then
		if spellframe.interestingCLEU[event] then 
			spellframe:COMBAT_LOG_EVENT_UNFILTERED(time, event, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname) 
		end
	end
end

function EventHorizon:LoadClassModule()
	local class = select(2,UnitClass('player'))
	class = class:sub(1,1)..class:sub(2):lower() -- 'WARLOCK' -> 'Warlock'
	local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo('EventHorizon_'..class)
	if not enabled or not loadable then 
		return 
	end
	local loaded, reason = LoadAddOn(name)
	if loaded and self.InitializeClass then
		return true
	end
end

--[[
spellid: number, rank doesn't matter
abbrev: string
config: table
{
	cast = <boolean>,
	channeled = <boolean>,
	numhits = <number of hits per channel>,
	cooldown = <boolean>,
	debuff = <boolean>,
	dot = <tick interval in s, requires debuff>,
	refreshable = <boolean>,
}
--]]
function EventHorizon:NewSpell(config)
	local spellid = config.spellID
	if type(spellid)~='number' then 
		return 
	end

	table.insert(self.barconfigs, config)
end

function EventHorizon:CreateSpellBar(config)
	local spellid = config.spellID
	local spellframe = CreateFrame('Frame', nil, mainframe)
	mainframe.numframes = mainframe.numframes+1
	mainframe:SetHeight(mainframe.numframes * (barheight+barspacing))

	local spellname, rank, tex = GetSpellInfo(spellid)
	--debug('creating frame for ',spellname)
	spellframe.spellname = spellname
	self[spellname] = spellframe

	-- Create the bar.
	spellframe.indicators = {}
	spellframe.unused = {}
	spellframe:SetBackdrop{bgFile = bartexture}
	spellframe:SetBackdropColor(unpack(barbgcolor))

	-- Create and set the spell icon.
	local icon = spellframe:CreateTexture(nil, 'BORDER')
	icon:SetTexture(tex)
	if not self.config.iconborder then
		local x=0.07
		local y=1-x
		icon:SetTexCoord(x,y,x,y)
	end
	spellframe.icon = icon

	local stacks = spellframe:CreateFontString(nil, 'OVERLAY')
	stacks:SetFontObject('NumberFontNormalSmall')
	spellframe.stacks = stacks

	local meta = getmetatable(spellframe)
	if meta and meta.__index then
		local metaindex = meta.__index
		setmetatable(spellframe, {__index = 
		function(self,k) 
			if spellbase[k] then 
				self[k]=spellbase[k] 
				return spellbase[k] 
			end 
			return metaindex[k]
		end})
	else
		setmetatable(spellframe, {__index = spellbase})
	end
	local interestingEvent = {}

	interestingEvent['UNIT_SPELLCAST_SENT'] = true
	if config.channeled then
		interestingEvent['UNIT_SPELLCAST_CHANNEL_START'] = true
		interestingEvent['UNIT_SPELLCAST_CHANNEL_STOP'] = true
		interestingEvent['UNIT_SPELLCAST_CHANNEL_UPDATE'] = true
		if config.numhits then
			-- Register for the CLEU tick event.
			spellframe.interestingCLEU = {SPELL_DAMAGE=true}
			spellframe.numhits = config.numhits
		end
	elseif config.cast then
		spellframe.cast = config.cast
		interestingEvent['UNIT_SPELLCAST_START'] = true
		interestingEvent['UNIT_SPELLCAST_STOP'] = true
		interestingEvent['UNIT_SPELLCAST_DELAYED'] = true
	end
	if config.cooldown then
		spellframe.cooldown = config.cooldown
		interestingEvent['SPELL_UPDATE_COOLDOWN'] = true
	end

	if config.debuff then
		spellframe.unique = config.unique
		spellframe.AuraFunction = UnitDebuff
		spellframe.auraunit = 'target'
		if type(config.debuff)=='number' then
			spellframe.auraname = (GetSpellInfo(config.debuff))
		elseif type(config.debuff)=='table' then
			spellframe.auraname = {}
			for i,id in ipairs(config.debuff) do
				tinsert(spellframe.auraname, (GetSpellInfo(id)))
			end
			spellframe.AuraFunction = UnitDebuffUnique
		else
			spellframe.auraname = spellname
		end
		interestingEvent['UNIT_AURA'] = true
		interestingEvent['PLAYER_TARGET_CHANGED'] = true
		spellframe.minstacks = config.minstacks
		if config.dot then
			spellframe.dot = config.dot
			spellframe.interestingCLEU = {SPELL_PERIODIC_DAMAGE=true}
			if config.refreshable then
				spellframe.UNIT_AURA = spellbase.UNIT_AURA_refreshable
				spellframe.PLAYER_TARGET_CHANGED = spellbase.PLAYER_TARGET_CHANGED_refreshable
				interestingEvent['PLAYER_REGEN_ENABLED'] = true
				spellframe.interestingCLEU.SPELL_CAST_SUCCESS=true
				spellframe.debuffs = {}
				spellframe.castsuccess = {}
			end
		end
	elseif config.playerbuff then
		spellframe.AuraFunction = UnitBuff
		spellframe.auraunit = 'player'
		spellframe.alwaysrefresh = true
		interestingEvent['UNIT_AURA'] = true
		if type(config.playerbuff)=='number' then
			spellframe.auraname = (GetSpellInfo(config.playerbuff))
		else
			spellframe.auraname = spellname
		end
		if config.internalcooldown then
			spellframe.internalcooldown = config.internalcooldown
		end
	end

	spellframe.interestingEvent = interestingEvent

	if config.stance then
		spellframe.stance = config.stance
	end
	
	spellframe:SetScript('OnEvent', EventHandler)
	spellframe:SetScript('OnUpdate', spellframe.OnUpdate)

	-- Layout
	icon:SetPoint('TOPLEFT', mainframe, 'TOPLEFT', 0, -(mainframe.numframes-1) * (barheight+barspacing))
	spellframe:SetPoint('TOPLEFT', icon, 'TOPRIGHT')
	spellframe:SetWidth(barwidth)
	spellframe:SetHeight(barheight)
	icon:SetWidth(barheight)
	icon:SetHeight(barheight)
	stacks:SetPoint('BOTTOMRIGHT', icon, 'BOTTOMRIGHT')
	stacks:SetJustifyH('RIGHT')

	spellframe:Activate()

	return spellframe
end

function EventHorizon:Activate(...)
	local activate = select('#',...) == 0 or ...
	--debug('Activate',activate, ...)
	if not activate then
		return self:Deactivate()
	end

	if self.isActive then
		return
	end

	mainframe:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
	mainframe:RegisterEvent(PLAYER_TALENT_UPDATE)
	mainframe:RegisterEvent('UPDATE_SHAPESHIFT_FORM')
	mainframe:RegisterEvent('UPDATE_SHAPESHIFT_FORMS')
	mainframe:RegisterEvent('SPELL_UPDATE_COOLDOWN')

	mainframe:Show()

	self.isActive = true
end

function EventHorizon:Deactivate()
	if self.isActive==false then
		return
	end
	mainframe:UnregisterAllEvents()
	mainframe:RegisterEvent(PLAYER_TALENT_UPDATE)

	mainframe:Hide()

	self.isActive = false
end

function spellbase:Deactivate()
	if not self.isActive then return end
	--debug('unregistering events for', self.spellname)
	self:UnregisterAllEvents()
	if self.interestingCLEU then
		mainframe.framebyspell[self.spellname] = nil
	end
	self:Hide()
	self.isActive = nil
end

function spellbase:Activate()
	if self.isActive then return end
	--debug('registering events for', self.spellname)
	for event in pairs(self.interestingEvent) do
		self:RegisterEvent(event)
	end
	if self.interestingCLEU then
		mainframe.framebyspell[self.spellname] = self
	end

	self:Show()
	self.isActive = true
end

--[[
Should only be called after the DB is loaded and spell and talent information is available.
--]]
function EventHorizon:Initialize()
	-- Make sure this function is called only once.
	self.Initialize = nil

	--debug('GetTalentInfo(1,1)',GetTalentInfo(1,1))
	playerguid = UnitGUID('player')
	if not playerguid then
		error('no playerguid')
	end
	self.db = EventHorizonDB
	mainframe.spellframes = activeFrames

	-- Create the main and spell frames.
	mainframe:SetHeight(1)
	mainframe.numframes = 0
	mainframe.framebyspell= {}
	mainframe:SetScript('OnEvent', EventHandler)
	mainframe:SetScale(self.config.scale or 1)

	if not self:LoadClassModule() then
		return
	end

	self:ApplyConfig()

	self:InitializeClass()

	mainframe:SetWidth(barwidth + barheight)
	self:CheckTalents()

	if self.config.backdrop then
		self:SpawnStyleFrame()		-- Spawn backdrop frame.
	end
		
	-- Create the indicator for the current time.
	-- Bugfix: When the UI scale is at a very low setting, textures with a width of 1
	-- were not visible in some resolutions.
	local effectiveScale = mainframe:GetEffectiveScale()
	if effectiveScale then
		onepixelwide = 1/effectiveScale
	end
	local nowIndicator = mainframe:CreateTexture('EventHorizonNowIndicator', 'BORDER')
	local nowleft = -past/(future-past)*barwidth-0.5 + barheight
	nowIndicator:SetPoint('BOTTOM',mainframe,'BOTTOM')
	nowIndicator:SetPoint('TOPLEFT',mainframe,'TOPLEFT', nowleft, 0)
	nowIndicator:SetWidth(onepixelwide)
	nowIndicator:SetTexture(1,1,1,1)

	local anchor = self.config.anchor or {'TOPRIGHT', 'EventHorizonHandle', 'BOTTOMRIGHT'}
	if anchor[2]=='EventHorizonHandle' then
		-- Create the handle to reposition the frame.
		local handle = CreateFrame('Frame', 'EventHorizonHandle', mainframe)
		handle:SetFrameStrata('HIGH')
		handle:SetWidth(10)
		handle:SetHeight(5)
		handle:EnableMouse(true)
		handle:RegisterForDrag('LeftButton')
		handle:SetScript('OnDragStart', function(handle, button) handle:StartMoving() end)
		handle:SetScript('OnDragStop', function(handle) 
			handle:StopMovingOrSizing()
			local a,b,c,d,e = handle:GetPoint(1)
			if type(b)=='frame' then
				b=b:GetName()
			end
			self.db.point = {a,b,c,d,e}
		end)
		handle:SetMovable(true)

		mainframe:SetPoint(unpack(anchor))
		handle:SetPoint(unpack(self.db.point))

		handle.tex = handle:CreateTexture(nil, 'BORDER')
		handle.tex:SetAllPoints()
		handle:SetScript('OnEnter',function(frame) frame.tex:SetTexture(1,1,1,1) end)
		handle:SetScript('OnLeave',function(frame) frame.tex:SetTexture(1,1,1,0.1) end)
		handle.tex:SetTexture(1,1,1,0.1)
	end

	gcdSpellName = self.config.gcdSpellID and (GetSpellInfo(self.config.gcdSpellID))
	if gcdSpellName and self.config.gcdStyle then
		-- Create the GCD indicator, register cooldown event.
		gcdIndicator = mainframe:CreateTexture('EventHorizonGCDIndicator', 'BORDER')
		local nowleft = -past/(future-past)*barwidth-0.5 + barheight
		gcdIndicator:SetPoint('BOTTOM',mainframe,'BOTTOM')
		gcdIndicator:SetPoint('TOP',mainframe,'TOP')
		gcdIndicator:Hide()

		if self.config.gcdStyle == 'line' then
			gcdIndicator:SetWidth(onepixelwide)
		else
			gcdIndicator:SetPoint('LEFT',mainframe,'LEFT', nowleft, 0)
		end

		local gcdColor = self.config.gcdColor or {.5,.5,.5,.3}
		gcdIndicator:SetTexture(unpack(gcdColor))
	end

	mainframe:SetPoint(unpack(anchor))

	SLASH_EVENTHORIZON1 = '/eventhorizon'
	SLASH_EVENTHORIZON2 = '/ehz'
	SlashCmdList['EVENTHORIZON'] = function() 
		if mainframe:IsShown() then
			mainframe:Hide()
		else 
			mainframe:Show()
		end
	end
end

function EventHorizon:ApplyConfig()
	past = self.config.past or -3
	future = self.config.future or 9
	barheight = self.config.height or 18
	barwidth = self.config.width or 150
	barspacing = self.config.spacing or 0
	scale = 1/(future-past)
	bartexture = self.config.bartexture or 'Interface\\Addons\\EventHorizon\\Smooth'
	barbgcolor = self.config.barbgcolor or {1,1,1,0.1}
	texturedbars = self.config.texturedbars
	local texturealpha = self.config.texturealphamultiplier or 1

	if texturedbars then
		for colorid,color in pairs(colors) do
			if colorid~='default' and color[4] then
				color[4] = texturealpha*color[4]
			end
		end
	end

	local default = layouts.default
	for typeid,layout in pairs(layouts) do
		if typeid~='default' then
			for k,v in pairs(default) do
				if layout[k]==nil then
					layout[k] = v
				end
			end
		end
		local topperc, bottomperc = -layout.top[3]/barheight
		if layout.bottom[1] == 'TOP' then
			bottomperc = -layout.bottom[3]/barheight
		else
			bottomperc = 1-layout.bottom[3]/barheight
		end
		layout.texcoords = {0, 1, topperc, bottomperc}
	end
end

do
	local frame = CreateFrame('Frame')
	frame:SetScript('OnEvent', EventHandler)

	local talentcheck = GetTalentInfo(1,1)
	if talentcheck then
		-- Talent information is already available, which means that the interface was reloaded.
		frame:RegisterEvent('PLAYER_LOGIN')
		function frame:PLAYER_LOGIN()
			EventHorizon:Initialize()
			self:UnregisterEvent('PLAYER_LOGIN')
		end
	else
		frame:RegisterEvent('PLAYER_ALIVE')
		function frame:PLAYER_ALIVE()
			EventHorizon:Initialize()
			self:UnregisterEvent('PLAYER_ALIVE')
		end
	end
end


function EventHorizon:SpawnStyleFrame()
	self.SpawnStyleFrame = nil
	local stylebg = self.config.bg or 'Interface\\ChatFrame\\ChatFrameBackground'
	local styleborder = self.config.border or 'Interface\\Tooltips\\UI-Tooltip-Border'
	local stylebgcolor = self.config.bgcolor or {0,0,0,0.5}
	local stylebordercolor = self.config.bordercolor or {1,1,1,1}
	local styleinset = self.config.inset or {top = 2, bottom = 2, left = 2, right = 2}
	local stylepadding = self.config.padding or 3
	local styleedge = self.config.edgesize or 8

	local styleframe = CreateFrame('Frame',nil,mainframe)
	styleframe:SetFrameStrata('BACKGROUND')
	styleframe:SetPoint('TOPRIGHT', mainframe, 'TOPRIGHT', stylepadding, stylepadding)
	styleframe:SetPoint('BOTTOMLEFT', mainframe, 'BOTTOMLEFT', -stylepadding, -stylepadding)
	styleframe:SetBackdrop({
		bgFile = stylebg,
		edgeFile = styleborder, tileSize = 0, edgeSize = styleedge,
		insets = styleinset,
	})
	styleframe:SetBackdropColor(unpack(stylebgcolor))
	styleframe:SetBackdropBorderColor(unpack(stylebordercolor))
end
